package baseFx.database.query.impl;

import baseFx.common.Stream;
import baseFx.common.utils.StringUtils;
import baseFx.database.CursorReader;
import baseFx.database.DbEntity;
import baseFx.database.SqlParam;
import baseFx.database.query.AbstractQuery;
import baseFx.database.query.ForUpdate;
import baseFx.database.query.SqlQuery;
import baseFx.database.where.QueryWhere;
import baseFx.database.where.impl.MysqlWhere;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MysqlQuery extends AbstractQuery {
    private SqlQuery query;

    public MysqlQuery(SqlQuery query) {
        super(query);
        this.query = query;
    }

    @Override
    protected <T> SqlParam onGets(QueryWhere<T> where, Integer timeout) {
        DbEntity entity = DbEntity.get(where.getTable());
        SqlParam param = new MysqlWhere(query).parse(where);
        List<String> olist = new ArrayList<>();
        Object model = where.asc();
        if (model != null) {
            Map<String, Object> values = entity.getValues(model);
            for (String name : values.keySet()) {
                olist.add(escapeField(name) + " asc");
            }
        }
        model = where.desc();
        if (model != null) {
            Map<String, Object> values = entity.getValues(model);
            for (String name : values.keySet()) {
                olist.add(escapeField(name) + " desc");
            }
        }
        String fields = where.fields();
        if ("*".equals(fields) == false) {
            List<String> fs = new ArrayList<>();
            String[] items = fields.split(",");
            for (String name : items) {
                if (entity.getFieldByPropName(name) != null) {
                    fs.add(escapeField(name));
                }
            }
            if (fs.isEmpty() == false) {
                fields = Stream.create(items).map(i -> escapeField(i)).join(",");
            }
        }
        StringBuilder sql = new StringBuilder().
                append("select ").append(fields).append(" from ").
                append(escapeField(getTableName(where.getTable())));
        if (StringUtils.hasValue(where.index())) {
            sql.append(" force index(`" + where.index() + "`)");
        }
        sql.append(param.command);
        if (olist.size() > 0) {
            sql.append(" order by ").append(String.join(",", olist));
        }
        Integer skip = where.skip();
        Integer take = where.take();
        if (skip != null && take != null) {
            sql.append(" limit ").append(skip).append(",").append(take);
        } else if (skip != null) {
            sql.append(" limit ").append(skip).append(",").append(Integer.MAX_VALUE);
        } else if (take != null) {
            sql.append(" limit ").append(take);
        }
        if (where.forUpdate() == ForUpdate.Lock) {
            sql.append(" for update");
        } else if (where.forUpdate() == ForUpdate.SkipLock) {
            sql.append(" for update skip locked");
        } else if (where.forUpdate() == ForUpdate.NoWait) {
            sql.append(" for update nowait");
        }
        param.timeout = timeout;
        param.command = sql.toString();
        return param;
    }

    @Override
    protected <T> SqlParam onGet(QueryWhere<T> where, Integer timeout) {
        QueryWhere<T> w = where.clone(s -> {
            s.set("skip", null);
            s.set("take", 1);
        });
        return onGets(w, timeout);
    }

    @Override
    protected <T> SqlParam onCount(QueryWhere<T> where, Integer timeout) {
        QueryWhere<T> w = where.clone(s -> {
            s.set("skip", null);
            s.set("take", null);
            s.set("fields", "count(1)");
            s.set("forUpdate", ForUpdate.NoLock);
        });
        return onGets(w, timeout);
    }

    @Override
    public <T> CursorReader<T> cursor(Class<T> clazz, SqlParam param, int fetchSize, boolean independent) {
        return query.cursor(clazz, param, fetchSize, independent);
    }
}
